﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile
{
	internal static class Util
	{
		public static string GetRowDelimiter(Compiler compiler)
		{
			return compiler.IsReadable ? "\n" : "`";
		}

		public static string GetColDelimiter(Compiler compiler)
		{
			return compiler.IsReadable ? " " : "|";
		}

		private static readonly string HEX_DIGITS = "0123456789abcdef";

		private static readonly Dictionary<char, int> HEX_VALUES = new Dictionary<char, int>()
		{
			{ '0', 0 }, { '1', 1 }, { '2', 2 }, { '3', 3 },
			{ '4', 4 }, { '5', 5 }, { '6', 6 }, { '7', 7 },
			{ '8', 8 }, { '9', 9 }, { 'a', 10 }, { 'b', 11 },
			{ 'c', 12 }, { 'd', 13 }, { 'e', 14 }, { 'f', 15 },

			// Probably unnecessary for the contexts this will be used...
			{ 'A', 10 }, { 'B', 11 }, { 'C', 12 }, { 'D', 13 },
			{ 'E', 14 }, { 'F', 15 },
		};

		public static int FromHex(string value)
		{
			int sign = 1;
			int start = 0;
			if (value[0] == '-')
			{
				sign = -1;
				start = 1;
			}

			int output = 0;

			for (int i = start; i < value.Length; ++i)
			{
				output = (output << 4) + HEX_VALUES[value[i]];
			}

			return output * sign;
		}

		public static string ToHex(Compiler c, int value)
		{
			if (c.IsReadable)
			{
				return value.ToString();
			}
			return ToHex(c, value, 0);
		}

		public static string ToHex(Compiler c, int value, int digits)
		{

			int sign = 1;
			if (value < 0)
			{
				sign = -1;
				value *= -1;
			}

			string output = "";

			if (c.IsReadable)
			{
				while (output.Length < digits)
				{
					output = "0" + output;
				}
			}
			else
			{
				if (value == 0)
				{
					output = "0";
				}
				else
				{
					while (value > 0)
					{
						int digit = value % 16;
						value = value >> 4;
						output = HEX_DIGITS[digit] + output;
					}
				}

				while (digits > output.Length)
				{
					output = "0" + output;
				}
			}

			return (sign == -1 ? "-" : "") + output;
		}

		public static string ConvertStringToHex(Compiler compiler, string value)
		{
			byte b;
			StringBuilder output = new StringBuilder();
			for (int i = 0; i < value.Length; ++i)
			{
				b = (byte)value[i];
				output.Append(ToHex(compiler, b, 2));
			}

			return output.ToString();
		}

		// The following generally safe characters were excluded due to potentially
		// unsafe behavior on certain platforms:
		// ' " \ % $
		// ` (backtick) is excluded because it is the delimiter for tables.
		// # is excluded because it is used for denoting escaped characters
		private static readonly HashSet<char> SAFE_CHARS = new HashSet<char>(
			("abcdefghijklmnopqrstuvwxyz" +
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
			"0123456789~!@^&*()-=_+" +
			"[]{}|;:,.<>/? ").ToCharArray()
			);

		public static string CreateStringTable(Compiler c, string[] values)
		{
			List<string> output = new List<string>();
			string value;
			for (int i = 0; i < values.Length; ++i)
			{
				value = EscapeStringForTable(c, values[i]);
				if (c.IsReadable)
				{
					value = i + ": " + value;
				}
				output.Add(value);
			}

			return string.Join(c.IsReadable ? "\n" : "`", output);
		}

		public static string EscapeStringForTable(Compiler compiler, string value)
		{
			if (compiler.IsReadable)
			{
				return value.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t");
			}

			List<string> output = new List<string>();
			char c;
			byte b;
			for (int i = 0; i < value.Length; ++i)
			{
				c = value[i];
				if (SAFE_CHARS.Contains(c))
				{
					output.Add("" + c);
				}
				else
				{
					b = (byte)c;
					output.Add("#" + ToHex(compiler, b, 2));
				}
			}

			return string.Join("", output);
		}

		public static IEnumerable<Nodes.Executable> GetExecutables(params Nodes.Executable[][] executableLists)
		{
			foreach (Nodes.Executable[] executableList in executableLists)
			{
				if (executableList != null)
				{
					foreach (Nodes.Executable executable in executableList)
					{
						yield return executable;
					}
				}
			}
		}
	}
}
